﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.Serialization;

namespace Kyoh.Lib.Component
{
    /// <summary>ソートと検索に対応した<see cref="BindingList{T}"/>の継承実装を提供します。</summary>
    /// <typeparam name="T">リストのアイテムの型。</typeparam>
    public class BindingListImpl<T> : BindingList<T>, IBindingList, IList<T>, ITypedList, ISerializable
    {
        private readonly IList _collectionList;
        /// <summary>BindingListImpl{T} クラスの新しいインスタンスを初期化します。</summary>
        public BindingListImpl()
        {
            _collectionList = (IList)Items;

        }
        /// <summary>BindingListImpl{T} クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="collection">内部リストを初期化する要素の列挙を表す <see cref="IEnumerable{T}"/>。</param>
        public BindingListImpl(IEnumerable<T> collection)
            : this(new List<T>(collection))
        {
            _collectionList = (IList)Items;
        }
        /// <summary>BindingListImpl{T} クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="list">内部リストを初期化する要素のリストを表す <see cref="IList{T}"/>。</param>
        public BindingListImpl(IList<T> list)
            : base(list)
        {
            _collectionList = (IList)Items;
        }
        
        /// <summary>リストの項目をシリアル化する際の要素名。</summary>
        public const string ItemElementName = "Item{0}";
        #region ISerializable メンバー
        /// <summary><see cref="BindingListImpl{T}"/> クラスの新しいインスタンスを初期化します。</summary>
        /// <param name="info"><see cref="BindingListImpl{T}"/> クラスのシリアル化済みオブジェクト データを保持している <see cref="SerializationInfo"/>。</param>
        /// <param name="context">転送元または転送先に関するコンテキスト情報を含んでいる <see cref="StreamingContext"/>。</param>
        protected BindingListImpl(SerializationInfo info, StreamingContext context)
        {
            for (var i = 0; i < info.MemberCount; i++)
                Add((T)info.GetValue(string.Format(ItemElementName, i), typeof(T)));
        }
        /// <summary><see cref="BindingListImpl{T}"/> クラスのシリアル化データを取得します。</summary>
        /// <param name="info"><see cref="BindingListImpl{T}"/> クラスのシリアル化済みオブジェクト データを格納する <see cref="SerializationInfo"/>。</param>
        /// <param name="context">転送元または転送先に関するコンテキスト情報を含んでいる <see cref="StreamingContext"/>。</param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            for (var i = 0; i < Count; i++)
                info.AddValue(string.Format(ItemElementName, i), this[i]);
        }
        #endregion
        #region IBindingList.Sort メンバ
        private PropertyDescriptor _sortProperty;
        private ListSortDirection _sortDirection;
        private bool _isSorted;
        /// <summary>内部のリストを取得します。</summary>
        protected List<T> List
        {
            get
            {
                if (Items is List<T>)
                    return Items as List<T>;
                if (Items is BindingListImpl<T>)
                    return (Items as BindingListImpl<T>).List;
                return null;
            }
        }
        /// <summary>リストにソートを適用します。</summary>
        /// <param name="prop">ソート基準となるプロパティ。</param>
        /// <param name="direction">ソート方向。</param>
        protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            var list = List;
            if (list == null)
            {
                _sortProperty = null;
                _sortDirection = direction;
                _isSorted = false;
            }
            else
            {
                var comparer = new PropertyComparer<T>(prop, direction);
                list.Sort(comparer);
                _sortProperty = prop;
                _sortDirection = direction;
                _isSorted = true;
            }

            OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }
        /// <summary>ソート基準となるプロパティを取得します。</summary>
        protected override PropertyDescriptor SortPropertyCore { get { return _sortProperty; } }
        /// <summary>ソート方向を取得します。</summary>
        protected override ListSortDirection SortDirectionCore { get { return _sortDirection; } }
        /// <summary>ソート基準となるプロパティを削除します。</summary>
        protected override void RemoveSortCore()
        {
            _sortProperty = null;
            _sortDirection = ListSortDirection.Ascending;
            _isSorted = false;
        }
        /// <summary>ソート済かどうかを取得します。</summary>
        protected override bool IsSortedCore { get { return _isSorted; } }
        /// <summary>ソートをサポートするかどうかを取得します。</summary>
        protected override bool SupportsSortingCore { get { return true; } }
        #endregion

        #region IBindingList.Search メンバ
        private readonly Dictionary<PropertyDescriptor, IEqualityComparer> _indices
            = new Dictionary<PropertyDescriptor, IEqualityComparer>();
        /// <summary>検索に用いるインデックスを追加します。</summary>
        /// <param name="prop">検索に用いるプロパティを表す <see cref="PropertyDescriptor"/>。</param>
        public void AddIndex(PropertyDescriptor prop)
        {
            if (_indices.ContainsKey(prop))
                return;

            var compType = typeof (EqualityComparer<>).MakeGenericType(prop.PropertyType);
            var compDef = compType.GetProperty("Default", BindingFlags.Public | BindingFlags.Static);
            var comparer = compDef.GetValue(compType, null) as IEqualityComparer;
            if (comparer != null)
                _indices.Add(prop, comparer);
        }
        /// <summary>指定されたプロパティを用いて、オブジェクトの検索を行います。</summary>
        /// <param name="prop">検索に用いるプロパティを表す <see cref="PropertyDescriptor"/>。</param>
        /// <param name="key">検索するプロパティ値。</param>
        /// <returns>指定の検索条件を満たすオブジェクトが見つかった場合には、そのインデックス値。それ以外の場合は <c>-1</c>。</returns>
        public int Find(PropertyDescriptor prop, object key)
        {
            var list = Items as List<T>;
            if (list == null)
                return -1;

            if (!_indices.ContainsKey(prop))
                AddIndex(prop);

            return _indices.ContainsKey(prop)
                ? list.FindIndex(x => _indices[prop].Equals(prop.GetValue(x), key))
                : list.FindIndex(x => prop.GetValue(x) == key);
        }

        /// <summary>検索に用いるプロパティを削除します。</summary>
        /// <param name="property">削除するプロパティを表す <see cref="PropertyDescriptor"/>。</param>
        public void RemoveIndex(PropertyDescriptor property)
        {
            _indices.Remove(property);
        }
        /// <summary>検索をサポートするかどうかを取得します。このクラスでは <c>true</c>。</summary>
        public bool SupportsSearching { get { return true; } }
        #endregion

        #region BindingList<T>, IBindingList, IList<T>メンバ
        /// <summary>編集を許可するかどうかを取得します。</summary>
        public new virtual bool AllowEdit { get { return base.AllowEdit; } }
        /// <summary>新しい行の追加を許可するかどうかを取得します。</summary>
        public new virtual bool AllowNew { get { return base.AllowNew; } }
        /// <summary>行の削除を許可するかどうかを取得します。</summary>
        public new virtual bool AllowRemove { get { return base.AllowRemove; } }
        /// <summary>リストの変更通知をサポートするかどうかを取得します。</summary>
        public bool SupportsChangeNotification { get { return base.SupportsChangeNotificationCore; } }
        /// <summary>リストが固定サイズかどうかを取得します。</summary>
        public virtual bool IsFixedSize { get { return _collectionList.IsFixedSize; } }
        /// <summary>リストが読み取り専用かどうかを取得します。</summary>
        public virtual bool IsReadOnly { get { return _collectionList.IsReadOnly; } }
        /// <summary>リストに要素を追加します。</summary>
        /// <param name="item">追加する要素。</param>
        public new virtual void Add(T item) { base.Add(item); }
        /// <summary>リストの指定位置に要素を挿入します。</summary>
        /// <param name="index">要素を挿入するインデックス。</param>
        /// <param name="item">挿入する要素。</param>
        public new virtual void Insert(int index, T item) { base.Insert(index, item); }
        /// <summary>リストから指定の要素を削除します。</summary>
        /// <param name="item">削除する要素。</param>
        /// <returns>リスト内に指定の要素が存在し、削除された場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
        public new virtual bool Remove(T item) { return base.Remove(item); }
        /// <summary>リスト内の指定位置の要素を削除します。</summary>
        /// <param name="index">削除する要素のインデックス。</param>
        public new virtual void RemoveAt(int index) { base.RemoveAt(index); }
        /// <summary>リストの要素を全て削除します。</summary>
        public new virtual void Clear() { base.Clear(); }
        /// <summary>リスト内の指定されたインデックスの要素を取得・設定します。</summary>
        /// <param name="index">取得・設定する要素のインデックス。</param>
        /// <returns>指定されたインデックスの要素。</returns>
        public new virtual T this[int index]
        {
            get { return base[index]; }
            set { base[index] = value; }
        }

        object IList.this[int index]
        {
            get { return base[index]; }
            set { base[index] = (T)value; }
        }
        int IList.Add(object value) { return _collectionList.Add(value); }
        bool IList.Contains(object value) { return _collectionList.Contains(value); }
        int IList.IndexOf(object value) { return _collectionList.IndexOf(value); }
        void IList.Insert(int index, object value) { _collectionList.Insert(index, value); }
        void IList.Remove(object value) { _collectionList.Remove(value); }
        void ICollection.CopyTo(Array array, int index) { _collectionList.CopyTo(array, index); }
        bool ICollection.IsSynchronized { get { return _collectionList.IsSynchronized; } }
        object ICollection.SyncRoot { get { return _collectionList.SyncRoot; } }
        #endregion

        #region ITypedList メンバー
        /// <summary>データをバインドするために使用される各項目のプロパティを表す <see cref="PropertyDescriptorCollection"/> オブジェクトを取得します。</summary>
        /// <param name="listAccessors">コレクション内で検索するバインド可能な <see cref="PropertyDescriptor"/> オブジェクトの配列。</param>
        /// <returns>データをバインドするために使用される各項目のプロパティを表す <see cref="PropertyDescriptorCollection"/> オブジェクト。</returns>
        public virtual PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
        {
            return TypeDescriptor.GetProperties(typeof(T));
        }
        /// <summary>リストの名前を取得します。</summary>
        /// <param name="listAccessors">リスト名を返す対象の <see cref="PropertyDescriptor"/> オブジェクトの配列。</param>
        /// <returns>リストの名前。</returns>
        public virtual string GetListName(PropertyDescriptor[] listAccessors)
        {
            return "BindedList";
        }
        #endregion

        #region IEnumerable<T> メンバー
        /// <summary>リスト内の要素を列挙します。</summary>
        /// <returns>リスト内の要素を列挙する <see cref="IEnumerator{T}"/>。</returns>
        public new IEnumerator<T> GetEnumerator() { return base.GetEnumerator(); }
        IEnumerator IEnumerable.GetEnumerator() { return base.GetEnumerator(); }
        #endregion
    }
}